#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#include <string>
/* XXX: Nasty compile Hack to work around the fact that ruby headers include regex.h */
#include </usr/include/regex.h>
#include "parser.h"
#define DEBUG
#include "ext_help.h"

#define PREALLOC_PARSE_BUFFER 128 // increase after we have debugged
#define PREALLOC_TAG_BUFFER 128

static char ESI_START_TERMINALS[] = {'<','e','s','i',':'};
static int ESI_START_TERMINALS_SIZE = sizeof(ESI_START_TERMINALS);

static int esi_parser_scan_for_esi_end( ESIParser *parser, const char *buffer, size_t length );
static int esi_parser_scan_for_esi_start( ESIParser *parser, const char *buffer, size_t length );
static int esi_parser_detect_tag( ESIParser *parser, const char *buffer, size_t length );
static int esi_parser_detect_end_sequence( ESIParser *parser, const char *buffer, size_t length );

static void check_buffer_size( char **buffer, size_t offset, size_t *size, size_t increment );
static void append_char_to_buffer( char **buffer, size_t *size, size_t *offset_end, char new_char );


ESIInclude *esi_include_new( const char *src, const char *alt, int max_ttl )
{
  ESIInclude *include = (ESIInclude*)malloc(sizeof(ESIInclude));

  include->src = strdup(src);
  if( alt ){
    include->alt = strdup(alt);
  }

  include->max_ttl = max_ttl;

  return include;
}

void esi_include_destroy( ESIInclude *include )
{
  if( include ){
    free(include->src);
    if( include->alt ){ free(include->alt); }
    free(include);
  }
}

ESIParser *esi_parser_new()
{
  ESIParser *parser = (ESIParser*)malloc(sizeof(ESIParser));

  parser->overflow_buffer_size = sizeof(char)*PREALLOC_PARSE_BUFFER;
  parser->overflow_buffer = (char*)malloc( parser->overflow_buffer_size );
  parser->overflow_buffer_offset = 0;
  memset( parser->overflow_buffer, '\0', parser->overflow_buffer_size );

  parser->stream_buffer_size = sizeof(char)*PREALLOC_PARSE_BUFFER;
  parser->stream_buffer = (char*)malloc( parser->stream_buffer_size );
  parser->stream_buffer_offset = 0;
  memset( parser->stream_buffer, '\0', parser->stream_buffer_size );
  
  parser->tag_buffer_size = sizeof(char)*PREALLOC_TAG_BUFFER;
  parser->tag_buffer = (char*)malloc( parser->tag_buffer_size );
  parser->tag_buffer_offset = 0;
  memset( parser->tag_buffer, '\0', parser->tag_buffer_size );
  
  parser->overflow = 0;
  parser->detect_tag = 0;
  parser->state = 0;
  
  parser->start_esi_tag = (regex_t*)malloc( sizeof(regex_t) );
  parser->esi_tag_attrs = (regex_t*)malloc( sizeof(regex_t) );
  parser->end_esi_tag = (regex_t*)malloc( sizeof(regex_t) );


  /*assert( regcomp((regex_t*)parser->start_esi_tag, "<esi:(\\w+)\\s*(.*?)>", REG_EXTENDED |REG_NOSUB) == 0 );
  assert( regcomp((regex_t*)parser->esi_tag_attrs, "([\\w-]+)\\s*=\\s*['\"](.+?)['\"]", REG_EXTENDED |REG_NOSUB) == 0 );
  assert( regcomp((regex_t*)parser->end_esi_tag, " </esi:(\\w+)>", REG_EXTENDED |REG_NOSUB) == 0 );
  */

  return parser;
}

void esi_parser_destroy( ESIParser *parser )
{
  TRACE();
  if( parser ){
    free(parser->stream_buffer);
    free(parser->overflow_buffer);
    regfree((regex_t*)parser->start_esi_tag);
    regfree((regex_t*)parser->esi_tag_attrs);
    regfree((regex_t*)parser->end_esi_tag);
    free( parser->start_esi_tag );
    free( parser->esi_tag_attrs );
    free( parser->end_esi_tag );
    free(parser);
  }
}

/*
 * increase the buffer size if offset + 1 is >= length
 */
static void check_buffer_size( char **buffer, size_t offset, size_t *length, size_t increment )
{
  if( (offset + increment) >= *length ){
//    printf("check_buffer_size: offset => %d, length => %d\n", offset, *length);
    *length *= 2;
    *length += increment; // to be safe add the increment
    *buffer = (char*)realloc( *buffer, *length );
    memset( (*buffer+offset), '\0', *length - offset );
  }
}

static void append_char_to_buffer( char **buffer, size_t *offset_end, size_t *size, char new_char )
{
//  printf("append_char_to_buffer: offset => %d, length => %d\n", *offset_end, *size);
  check_buffer_size( buffer, *offset_end, size, 1 );
  (*buffer)[(*offset_end)++] = new_char;
}

static int has_esi_start_tag_sequence( const char *buffer, size_t length )
{
  const char *ptr = (char*)memchr( buffer, ESI_START_TERMINALS[0], length );
  int remainder = 0;
  int has_start_sequence = 0;


  if( ptr ){
    remainder = (length - (ptr-buffer));
    has_start_sequence = 1;

    if( remainder > 0 && *(ptr+1) == ESI_START_TERMINALS[1] ){

      if( remainder > 1 )
        has_start_sequence &= *(ptr+2) == ESI_START_TERMINALS[2];
      else if( remainder > 2 )
        has_start_sequence &= *(ptr+3) == ESI_START_TERMINALS[3];
      else if( remainder > 3 )
        has_start_sequence = 0;
    }
  }
  return has_start_sequence;
}

static int esi_parser_scan_for_esi_start( ESIParser *parser, const char *buffer, size_t length )
{
  size_t i;
  char ch;

  for( i = 0; i < length; ++i ){
    ch = buffer[i];
    append_char_to_buffer( &(parser->stream_buffer), &(parser->stream_buffer_offset), &(parser->stream_buffer_size), ch );

    // if we found a terminal character
    if( ch == ':' && parser->stream_buffer_offset > 4 ){
      // start copying the data from the stream buffer into the overflow buffer
      if( !strncmp( parser->stream_buffer+parser->stream_buffer_offset - 5, "<esi:", 5 ) ){
        parser->stream_buffer_offset -= 5;
        //parser->stream_buffer[parser->stream_buffer_offset+1] = '\0';
        // copy the bytes into the overflow buffer and set stream_buffer_offset to j
        printf( "switching to overflow: [%d]\n", parser->stream_buffer_offset );
        memcpy( parser->overflow_buffer, (parser->stream_buffer + parser->stream_buffer_offset), 5 );
        parser->overflow_buffer_offset = 5;
        printf( "overflow: [%s]\n", parser->overflow_buffer );
        parser->overflow = 1; // change the parser state to overflow
        parser->detect_tag = 1;
        break;
      }
    }
  }

  if( parser->overflow ){
    esi_parser_scan_for_esi_end( parser, buffer + i, length - i );
    return 1;
  }
  else{
    return !has_esi_start_tag_sequence( parser->stream_buffer, parser->stream_buffer_offset );
  }
}

/*
 *
 * parsing an esi block, could be as simple as <esi:include attrs/>
 * or as complex as 
 *
 * <esi:try>
 *   <esi:attempt>
 *     <esi:include attrs/>
 *   </esi:attempt>
 *   <esi:except>
 *     <esi:include attrs/>
 *   </esi:except>
 * </esi:try>
 *
 * This method will not worry about what is in the esi block it will simply do the following
 * the first set of chars up to the first space found is the tag.  If there is no / before the first, closing >
 * then buffer in overflow_buffer until </esi:tag> is found, where tag is 
 * the set of chars found before the first space character or closing / or >
 * e.g. /[ />]/
 *
 */
static int esi_parser_scan_for_esi_end( ESIParser *parser, const char *buffer, size_t length )
{

  if( parser->detect_tag ){
    return esi_parser_detect_tag( parser, buffer, length );
  }
  else{
    return esi_parser_detect_end_sequence( parser, buffer, length );
  }
}

static int esi_parser_detect_tag( ESIParser *parser, const char *buffer, size_t length )
{
  size_t i;
  char ch;

  for( i = 0; i < length; ++i ){
    ch = buffer[i];
    if( isspace( ch ) ){
      parser->detect_tag = 0;
      break;
    }
    if( ch != ':' && ch != '>' ){
      // copy into both tag and overflow buffer, we'll want the tag buffer for end state detection
      append_char_to_buffer( &(parser->tag_buffer), &(parser->tag_buffer_offset), &(parser->tag_buffer_size), ch );
    }
    // we'll pass the overflow buffer to the user for esi tag block processing
    append_char_to_buffer( &(parser->overflow_buffer), &(parser->overflow_buffer_offset), &(parser->overflow_buffer_size), ch );
  }

  parser->found_close_mark = 0;

  return 0;
}

static int esi_parser_detect_end_sequence( ESIParser *parser, const char *buffer, size_t length )
{
  size_t i;
  char ch;
 
  for( i = 0; i < length; ++i ){
    ch = buffer[i];
    if( ch == '>' ){
      if( !parser->found_close_mark && parser->overflow_buffer[parser->overflow_buffer_offset] == '/' ){
        parser->found_close_mark = 1;
        /* finished processing the esi block */
        return 2;
      }
      else {
        printf( "Tag => [%s]\n", parser->tag_buffer );
        printf( "Match => [%s]\n", parser->overflow_buffer+(parser->overflow_buffer_offset-parser->tag_buffer_offset) );
        // look back tag_buffer_offset characters and see if there is a /
        if( !strncmp(parser->overflow_buffer+(parser->overflow_buffer_offset-parser->tag_buffer_offset), 
                     parser->tag_buffer, parser->tag_buffer_offset) ){
          return 2;
        }
      }
    }
    append_char_to_buffer( &(parser->overflow_buffer), &(parser->overflow_buffer_offset), &(parser->overflow_buffer_size), ch );
  }

  return 0;
}

/* 
 *
 * return (int)
 *
 * 2 is esi include content ready for processing
 *
 * 1 is data ready for streaming
 *
 * 0 buffer finished processing
 *
 */
int esi_parser_scan( ESIParser *parser, const char *buffer, size_t length )
{
  return 0;
}

void esi_parser_read_stream_buffer( ESIParser *parser, char **buffer, size_t *length )
{
  *buffer = parser->stream_buffer;
  *length = parser->stream_buffer_offset;
  parser->stream_buffer_offset = 0; // update offset position
}

void esi_parser_read_esi_block_buffer( ESIParser *parser, char **buffer, size_t *length )
{
  *buffer = parser->overflow_buffer;
  *length = parser->overflow_buffer_offset;
  parser->overflow_buffer_offset = 0; // update offset position
}
